#include "vanila/object.h"
#include "vanila/value.h"
#include "vanila/virtualmachine.h"
#include <iostream>

namespace vanila
{
static VirtualMachine* vm = utils::Singleton<VirtualMachine>::instance();

ObjectType Value::objectType() const noexcept
{ return this->_object->type(); }

#define VALUE_IS_AND_AS_SPECIFY_OBJECT(ObjectName)\
bool Value::is##ObjectName() const noexcept\
{ return this->isObject() && this->_object->is##ObjectName(); }\
Object##ObjectName* Value::as##ObjectName() const noexcept\
{ return this->_object->as##ObjectName(); }

VALUE_IS_AND_AS_SPECIFY_OBJECT(BoundMethod)
VALUE_IS_AND_AS_SPECIFY_OBJECT(Class)
VALUE_IS_AND_AS_SPECIFY_OBJECT(Closure)
VALUE_IS_AND_AS_SPECIFY_OBJECT(Dictionary)
VALUE_IS_AND_AS_SPECIFY_OBJECT(Function)
VALUE_IS_AND_AS_SPECIFY_OBJECT(Instance)
VALUE_IS_AND_AS_SPECIFY_OBJECT(List)
VALUE_IS_AND_AS_SPECIFY_OBJECT(Upvalue)
VALUE_IS_AND_AS_SPECIFY_OBJECT(Native)
VALUE_IS_AND_AS_SPECIFY_OBJECT(String)
VALUE_IS_AND_AS_SPECIFY_OBJECT(Set)

//! \brief print the value to console
//! \param[in] callStr wheather call '__str__'
void Value::print(bool callStr) const
{
    switch (this->_type)
    {
    case ValueType::EMPTY:      break;
    case ValueType::NIL:        std::cout << "NIL"; break;
    case ValueType::BOOL:       std::cout << (this->_boolean ? "true" : "false"); break;
    case ValueType::INTEGER:    std::cout << this->_integer; break;
    case ValueType::DECIMAL:    std::cout << this->_decimal; break;
    case ValueType::OBJECT:     this->_object->print(callStr); break;
    }
}

//! \brief get the value's hash value
int64_t Value::hash() const
{
    switch (this->_type)
    {
    case ValueType::NIL:        return -9223363242693287297;
    case ValueType::BOOL:       return (this->_boolean ? 1LL : 0LL);
    case ValueType::INTEGER:    return this->_integer;
    case ValueType::DECIMAL:    return *reinterpret_cast<const int64_t*>(&(this->_decimal));
    case ValueType::OBJECT:     return this->_object->hash();
    }
    return 0;
}

//! \brief wheather this == that
bool Value::equal(const Value& that) const
{
    ValueType type1 = this->type();
    ValueType type2 = that.type();

    if (type1 != type2)
    {
        if (type1 == ValueType::INTEGER && type2 == ValueType::DECIMAL)
            return this->integer() == that.decimal();
        else if (type1 == ValueType::DECIMAL && type2 == ValueType::INTEGER)
            return this->decimal() == that.integer();
        return false;
    }
    
    switch (type1)
    {
    case ValueType::NIL:        return true;
    case ValueType::BOOL:       return this->boolean() == that.boolean();
    case ValueType::INTEGER:    return this->integer() == that.integer();
    case ValueType::DECIMAL:    return this->decimal() == that.decimal();
    case ValueType::OBJECT:     return this->object()->equal(that.object());
    }
    return false;
}

//! \brief wheather this < that
bool Value::less(const Value& that) const
{
    ValueType type1 = this->type();
    ValueType type2 = that.type();

    if (type1 != type2)
    {
        if (type1 == ValueType::INTEGER && type2 == ValueType::DECIMAL)
            return this->integer() < that.decimal();
        else if (type1 == ValueType::DECIMAL && type2 == ValueType::INTEGER)
            return this->decimal() < that.integer();
        vm->runtimeError("Cannot use '<' on two different types of value objects");
        return false;
    }
    
    switch (type1)
    {
    case ValueType::NIL:        
    {
        vm->runtimeError("Cannot use '<' between 'NIL'");
        return false;
    }
    case ValueType::BOOL:       return this->boolean() < that.boolean();
    case ValueType::INTEGER:    return this->integer() < that.integer();
    case ValueType::DECIMAL:    return this->decimal() < that.decimal();
    case ValueType::OBJECT:     return this->object()->less(that.object());
    }
    return false;
}

//! \brief wheather this > that
bool Value::greater(const Value& that) const
{
    ValueType type1 = this->type();
    ValueType type2 = that.type();

    if (type1 != type2)
    {
        if (type1 == ValueType::INTEGER && type2 == ValueType::DECIMAL)
            return this->integer() > that.decimal();
        else if (type1 == ValueType::DECIMAL && type2 == ValueType::INTEGER)
            return this->decimal() > that.integer();
        vm->runtimeError("Cannot use '>' on two different types of value objects");
        return false;
    }
    
    switch (type1)
    {
    case ValueType::NIL:        
    {
        vm->runtimeError("Cannot use '>' between 'NIL'");
        return false;
    }
    case ValueType::BOOL:       return this->boolean() > that.boolean();
    case ValueType::INTEGER:    return this->integer() > that.integer();
    case ValueType::DECIMAL:    return this->decimal() > that.decimal();
    case ValueType::OBJECT:     return this->object()->greater(that.object());
    }
    return false;
}

//! \brief add two value
Value Value::add(const Value& that) const
{
    ValueType type1 = this->type();
    ValueType type2 = that.type();

    if (type1 != type2)
    {
        if (type1 == ValueType::INTEGER && type2 == ValueType::DECIMAL)
            return Value{this->integer() + that.decimal()};
        else if (type1 == ValueType::DECIMAL && type2 == ValueType::INTEGER)
            return Value{this->decimal() + that.integer()};
        vm->runtimeError("Cannot use '+' on two different types of value objects");
        return Value{};
    }
    
    switch (type1)
    {
    case ValueType::NIL:        
    {
        vm->runtimeError("Cannot use '+' between 'NIL'");
        return false;
    }
    case ValueType::BOOL:       return (!this->boolean() && !that.boolean()) ? Value{false} : Value{true};
    case ValueType::INTEGER:    return Value{this->integer() + that.integer()};
    case ValueType::DECIMAL:    return Value{this->decimal() + that.decimal()};
    case ValueType::OBJECT:     return this->object()->add(that.object());
    }
    return Value{};
}

//! \brief sub two value
Value Value::sub(const Value& that) const
{
    ValueType type1 = this->type();
    ValueType type2 = that.type();

    if (type1 != type2)
    {
        if (type1 == ValueType::INTEGER && type2 == ValueType::DECIMAL)
            return Value{this->integer() - that.decimal()};
        else if (type1 == ValueType::DECIMAL && type2 == ValueType::INTEGER)
            return Value{this->decimal() - that.integer()};
        vm->runtimeError("Cannot use '-' on two different types of value objects");
        return Value{};
    }
    
    switch (type1)
    {
    case ValueType::NIL:        
    {
        vm->runtimeError("Cannot use '-' between 'NIL'");
        return false;
    }
    case ValueType::BOOL: 
    {
        vm->runtimeError("Cannot use '-' between 'BOOL'");
        return false;
    }  
    case ValueType::INTEGER:    return Value{this->integer() - that.integer()};
    case ValueType::DECIMAL:    return Value{this->decimal() - that.decimal()};
    case ValueType::OBJECT:     return this->object()->sub(that.object());
    }
    return Value{};
}

//! \brief mul two value
Value Value::mul(const Value& that) const
{
    ValueType type1 = this->type();
    ValueType type2 = that.type();

    if (type1 != type2)
    {
        if (type1 == ValueType::INTEGER && type2 == ValueType::DECIMAL)
            return Value{this->integer() * that.decimal()};
        else if (type1 == ValueType::DECIMAL && type2 == ValueType::INTEGER)
            return Value{this->decimal() * that.integer()};
        vm->runtimeError("Cannot use '*' on two different types of value objects");
        return Value{};
    }
    
    switch (type1)
    {
    case ValueType::NIL:        
    {
        vm->runtimeError("Cannot use '*' between 'NIL'");
        return false;
    }
    case ValueType::BOOL: 
    {
        vm->runtimeError("Cannot use '*' between 'BOOL'");
        return false;
    }  
    case ValueType::INTEGER:    return Value{this->integer() * that.integer()};
    case ValueType::DECIMAL:    return Value{this->decimal() * that.decimal()};
    case ValueType::OBJECT:     return this->object()->mul(that.object());
    }
    return Value{};
}

//! \brief div two value
Value Value::div(const Value& that) const
{
    ValueType type1 = this->type();
    ValueType type2 = that.type();

    if (type1 != type2)
    {
        if (type1 == ValueType::INTEGER && type2 == ValueType::DECIMAL)
            return Value{this->integer() / that.decimal()};
        else if (type1 == ValueType::DECIMAL && type2 == ValueType::INTEGER)
            return Value{this->decimal() / that.integer()};
        vm->runtimeError("Cannot use '/' on two different types of value objects");
        return Value{};
    }
    
    switch (type1)
    {
    case ValueType::NIL:        
    {
        vm->runtimeError("Cannot use '/' between 'NIL'");
        return false;
    }
    case ValueType::BOOL: 
    {
        vm->runtimeError("Cannot use '/' between 'BOOL'");
        return false;
    }  
    case ValueType::INTEGER:    return Value{ static_cast<double>(this->integer()) / static_cast<double>(that.integer())};
    case ValueType::DECIMAL:    return Value{this->decimal() / that.decimal()};
    case ValueType::OBJECT:     return this->object()->div(that.object());
    }
    return Value{};
}

bool operator > (const Value& v1, const Value& v2)
{  
    return v1.hash() > v2.hash();
}

bool operator < (const Value& v1, const Value& v2)
{
    return v1.hash() < v2.hash();
}

}